// (C) Szymon Piechowicz, February 2012

#include "lights.h"
#include <avr/interrupt.h>
#include "adc_manager.h"
#include "common.h"


#define LIGHTS_PORT PORTC
#define LIGHTS_PORT_DIR DDRC
#define LIGHTS_BIT PC7
#define LIGHTS_DIRECTIONALS_L_PORT PORTC
#define LIGHTS_DIRECTIONALS_L_PORT_DIR DDRC
#define LIGHTS_DIRECTIONALS_L_BIT PC6
#define LIGHTS_DIRECTIONALS_R_PORT PORTC
#define LIGHTS_DIRECTIONALS_R_PORT_DIR DDRC
#define LIGHTS_DIRECTIONALS_R_BIT PC5
#define LIGHTS_SPOTLIGHT_PORT PORTC
#define LIGHTS_SPOTLIGHT_PORT_DIR DDRC
#define LIGHTS_SPOTLIGHT_BIT PC4
#define LIGHTS_PHOTORESISTOR_PORT PORTA
#define LIGHTS_PHOTORESISTOR_PORT_DIR DDRA
#define LIGHTS_PHOTORESISTOR_BIT PA6
#define LIGHTS_DARKNESS_LED_PORT PORTA
#define LIGHTS_DARKNESS_LED_PORT_DIR DDRA
#define LIGHTS_DARKNESS_LED_BIT PA0

#define BRIGHTNESS_ADC_DARK_THRESHOLD 80
#define BRIGHTNESS_ADC_MIN_CHANGE 20


// Does nothing if already started.
static void directionals_timer_start(void);
static void directionals_timer_stop(void);
static void brightness_adc_callback(int8_t bit, uint8_t result);


static bool is_directionals_timer_active = false;
static int8_t directionals_timer_postscaler;
static bool are_directionals_l_blinking = false;
static bool are_directionals_r_blinking = false;
static lights_brightness_callback brightness_callback;
// Callbacks are fired only when brightness state changes.
static bool brightness_did_report_first_result;
static bool brightness_last_reported_result;


void lights_init(void)
{
  PORT_OFF(LIGHTS_PORT, LIGHTS_BIT);
  PORT_DIR_OUT(LIGHTS_PORT_DIR, LIGHTS_BIT);
  PORT_OFF(LIGHTS_DIRECTIONALS_L_PORT, LIGHTS_DIRECTIONALS_L_BIT);
  PORT_DIR_OUT(LIGHTS_DIRECTIONALS_L_PORT_DIR, LIGHTS_DIRECTIONALS_L_BIT);
  PORT_OFF(LIGHTS_DIRECTIONALS_R_PORT, LIGHTS_DIRECTIONALS_R_BIT);
  PORT_DIR_OUT(LIGHTS_DIRECTIONALS_R_PORT_DIR, LIGHTS_DIRECTIONALS_R_BIT);
  PORT_OFF(LIGHTS_SPOTLIGHT_PORT, LIGHTS_SPOTLIGHT_BIT);
  PORT_DIR_OUT(LIGHTS_SPOTLIGHT_PORT_DIR, LIGHTS_SPOTLIGHT_BIT);
  PORT_OFF(LIGHTS_PHOTORESISTOR_PORT, LIGHTS_PHOTORESISTOR_BIT);
  PORT_OFF(LIGHTS_DARKNESS_LED_PORT, LIGHTS_DARKNESS_LED_BIT);
  PORT_DIR_OUT(LIGHTS_DARKNESS_LED_PORT_DIR, LIGHTS_DARKNESS_LED_BIT);
  
  TIMSK |= (1 << TOIE0); // enable overflow interrupt
}

void lights_on(void)
{
  PORT_ON(LIGHTS_PORT, LIGHTS_BIT);
}

void lights_off(void)
{
  PORT_OFF(LIGHTS_PORT, LIGHTS_BIT);
}

void lights_directionals_l_blinking_on(void)
{
  are_directionals_l_blinking = true;
  PORT_ON(LIGHTS_DIRECTIONALS_L_PORT, LIGHTS_DIRECTIONALS_L_BIT);
  directionals_timer_start();
}

void lights_directionals_l_blinking_off(void)
{
  are_directionals_l_blinking = false;
  PORT_OFF(LIGHTS_DIRECTIONALS_L_PORT, LIGHTS_DIRECTIONALS_L_BIT);
  if (!are_directionals_r_blinking)
    directionals_timer_stop();
}

void lights_directionals_r_blinking_on(void)
{
  are_directionals_r_blinking = true;
  PORT_ON(LIGHTS_DIRECTIONALS_R_PORT, LIGHTS_DIRECTIONALS_R_BIT);
  directionals_timer_start();
}

void lights_directionals_r_blinking_off(void)
{
  are_directionals_r_blinking = false;
  PORT_OFF(LIGHTS_DIRECTIONALS_R_PORT, LIGHTS_DIRECTIONALS_R_BIT);
  if (!are_directionals_l_blinking)
    directionals_timer_stop();
}

void lights_directionals_both_blinking_on(void)
{
  lights_directionals_l_blinking_on();
  lights_directionals_r_blinking_on();
}

void lights_directionals_both_blinking_off(void)
{
  lights_directionals_l_blinking_off();
  lights_directionals_r_blinking_off();
}

void lights_spotlight_on(void)
{
  PORT_ON(LIGHTS_SPOTLIGHT_PORT, LIGHTS_SPOTLIGHT_BIT);
}

void lights_spotlight_off(void)
{
  PORT_OFF(LIGHTS_SPOTLIGHT_PORT, LIGHTS_SPOTLIGHT_BIT);
}

void lights_start_measuring_brightness(lights_brightness_callback callback)
{
  brightness_callback = callback;
  brightness_did_report_first_result = false;
  adc_manager_start_measuring(LIGHTS_PHOTORESISTOR_BIT,
      brightness_adc_callback, BRIGHTNESS_ADC_MIN_CHANGE);
}

void lights_stop_measuring_brightness(void)
{
  adc_manager_stop_measuring(LIGHTS_PHOTORESISTOR_BIT);
}

static void directionals_timer_start(void)
{
  if (is_directionals_timer_active)
    return;
  TCCR0 |= (1 << CS02) | (1 << CS00); // prescaler / 1024
  TCNT0 = 0;
  directionals_timer_postscaler = 0;
  is_directionals_timer_active = true;
}

static void directionals_timer_stop(void)
{
  TCCR0 &= ~(1 << CS02) & ~(1 << CS00); // no clock, timer stopped
  is_directionals_timer_active = false;
}

static void brightness_adc_callback(int8_t bit, uint8_t result)
{
  bool is_bright = (result <= BRIGHTNESS_ADC_DARK_THRESHOLD);
  if (!brightness_did_report_first_result
      || is_bright != brightness_last_reported_result)
  {
    brightness_did_report_first_result = true;
    brightness_last_reported_result = is_bright;
    if (brightness_callback != NULL)
      brightness_callback(is_bright);
    if (is_bright)
      PORT_OFF(LIGHTS_DARKNESS_LED_PORT, LIGHTS_DARKNESS_LED_BIT);
    else
      PORT_ON(LIGHTS_DARKNESS_LED_PORT, LIGHTS_DARKNESS_LED_BIT);
  }
}

ISR(TIMER0_OVF_vect)
{
  ++directionals_timer_postscaler;
  if (directionals_timer_postscaler < 20)
    return;
  directionals_timer_postscaler = 0;
  if (are_directionals_l_blinking)
    PORT_TOGGLE(LIGHTS_DIRECTIONALS_L_PORT, LIGHTS_DIRECTIONALS_L_BIT);
  if (are_directionals_r_blinking)
    PORT_TOGGLE(LIGHTS_DIRECTIONALS_R_PORT, LIGHTS_DIRECTIONALS_R_BIT);
}